﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IntelliLance.Math.Parser
{
    /// <summary>
    /// Provides the default implementation for the ITokenProcessor interface, this implementation is based on the Sulting-yard Algorithm.
    /// </summary>
    public class SYATokenProcessor : ITokenProcessor
    {
        private readonly Queue<Token> _expressions = new Queue<Token>();
        private readonly Stack<Token> _operators = new Stack<Token>();
        private readonly Queue<Token> _tempOp = new Queue<Token>();

        /// <summary>
        /// Process the current token.
        /// </summary>
        public void ProcessToken(TokenContext context)
        {
            var CurrentToken = context.Token;

            if (CurrentToken is LeftParenthesisToken)
                _operators.Push(CurrentToken);
            else if (CurrentToken is RightParenthesisToken)
            {
                while (CheckLength() && !(_operators.Peek() is LeftParenthesisToken))
                    _expressions.Enqueue(_operators.Pop());

                if (CheckLength() && _operators.Peek() is LeftParenthesisToken)
                    _tempOp.Enqueue(_operators.Pop());

                if (CheckLength() && _operators.Peek() is FunctionToken)
                    _expressions.Enqueue(_operators.Pop());
            }
            else if (CurrentToken is ConstantToken || CurrentToken is VariableToken)
            {
                _expressions.Enqueue(CurrentToken);
            }

            else if (CurrentToken is FunctionToken)
            {
                _operators.Push(CurrentToken);
            }

            else if (CurrentToken is OperatorToken)
            {
                while (CheckLength() && _operators.Peek() is OperatorToken)
                {
                    var token1 = CurrentToken as OperatorToken;
                    if (token1.IsBinary)
                    {
                        var token2 = _operators.Peek() as OperatorToken;
                        var pre1 = CalculatePrecedence(token1.Value.ToString());
                        var pre2 = CalculatePrecedence(token2.Value.ToString());

                        if ((token2.IsLeftAssociative && pre1 <= pre2) ||
                            (!token1.IsLeftAssociative && pre1 < pre2))
                        {
                            _expressions.Enqueue(_operators.Pop());
                        }
                    }
                    else
                        break;

                }
                _operators.Push(CurrentToken);
            }

            else if (CurrentToken is FunctionParameterSeparatorToken)
            {
                while (CheckLength() && !(_operators.Peek() is LeftParenthesisToken))
                    _expressions.Enqueue(_operators.Pop());
            }

        }

        /// <summary>
        /// Checks whether the expressions stack is empty or not.
        /// </summary>
        /// <returns>True, if the stack is empty, otherwise, false.</returns>
        private bool CheckLength()
        {
            return _operators.Count > 0;
        }

        /// <summary>
        /// Calculate the precedence for a spcific operator.
        /// </summary>
        /// <returns>An Integer that represents the precedence of th current operator.</returns>
        private int CalculatePrecedence(string @operator)
        {
            switch (@operator)
            {
                case "!":
                    return 4;
                case "*":
                case "/":
                case "%":
                    return 3;
                case "+":
                case "-":
                    return 2;
                case "=":
                    return 1;
            }
            return 0;
        }

        /// <summary>
        /// Pops up everything in the stack to the queue. and returns the queue to the caller.
        /// </summary>
        /// <returns>An instance of Queue(of Token).</returns>
        public Queue<Token> Eval()
        {
            while (CheckLength())
            {
                if (_operators.Peek() is LeftParenthesisToken)
                {
                    throw new ParserSyntaxException("Mismatch parenthesis");
                }
                else
                    _expressions.Enqueue(_operators.Pop());
            }
            return _expressions;
        }
    }
}
